﻿using MongoDB.Bson.Serialization;
using MongoDB.Driver;
using MongoDB.Driver.Builders;
using MongoDB.Driver.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using Sustainalytics.LongRunningTaskManager.Interface;

namespace Sustainalytics.LongRunningTaskManager
{
    /// <summary>
    /// Repository for long running task instance snapshots.
    /// </summary>
    public class LongRunningTaskInstanceSnapshotRepository : ILongRunningTaskInstanceSnapshotRepository
    {
        // collection name used with current repository
        private const string CollectionName = "LongRunningTaskInstanceSnapshot";

        // task result factory used to create task results
        private readonly ILongRunningTaskResultFactory _taskResultFactory;

        private MongoClient _client { get; set; }
        private IMongoDatabase _database { get; set; }

        /// <summary>
        /// Ctor.
        /// </summary>
        /// <param name="connectionString"> Connection string. </param>
        /// <param name="taskResultFactory"> Task result factory used to create task results. </param>
        public LongRunningTaskInstanceSnapshotRepository(string connectionString, ILongRunningTaskResultFactory taskResultFactory)
        {
            _taskResultFactory = taskResultFactory;

            // DB address
            var mongoUrl = MongoUrl.Create(connectionString);

            if (string.IsNullOrWhiteSpace(mongoUrl.DatabaseName))
            {
                throw new ArgumentException("No database specified in the connection string!");
            }

            // Mongo DB client
            _client = new MongoClient(mongoUrl);
            // Mongo DB
            _database = _client.GetDatabase(mongoUrl.DatabaseName);

            LoadClassMaps(taskResultFactory);
        }

        private void LoadClassMaps(ILongRunningTaskResultFactory taskResultFactory)
        {
            // get all registered types
            var types = taskResultFactory.GetTaskResultTypes();

            // register one class map per type to allow deserializing unknown types
            foreach (var type in types)
            {
                BsonClassMap.LookupClassMap(type);
            }
        }

        /// <summary>
        /// Get snapshot by ID.
        /// </summary>
        /// <param name="id"> Snapshot ID. </param>
        /// <returns> Task instance snapshot. </returns>
        public LongRunningTaskInstanceSnapshot Get(string id)
        {
            return _database.GetCollection<LongRunningTaskInstanceSnapshot>(CollectionName).AsQueryable().FirstOrDefault(s => s.Id == id);
        }

        /// <summary>
        /// Get all task instances snapshots.
        /// </summary>
        /// <returns></returns>
        public List<LongRunningTaskInstanceSnapshot> GetAll()
        {
            return _database.GetCollection<LongRunningTaskInstanceSnapshot>(CollectionName).AsQueryable().OrderBy(el => el.Order).ToList();
        }

        /// <summary>
        /// Insert task instance snapshot.
        /// </summary>
        /// <param name="snapshot"> Task instance snapshot. </param>
        public void Insert(LongRunningTaskInstanceSnapshot snapshot)
        {
            if (snapshot == null)
            {
                throw new LongRunningTaskInstanceSnapshotNullException();
            }

            _database.GetCollection<LongRunningTaskInstanceSnapshot>(CollectionName).InsertOne(snapshot);
        }

        /// <summary>
        /// Insert multiple task instance snapshots.
        /// </summary>
        /// <param name="snapshots"> Task instance snapshots. </param>
        public void Insert(IEnumerable<LongRunningTaskInstanceSnapshot> snapshots)
        {
            if (snapshots == null)
            {
                throw new LongRunningTaskInstanceSnapshotNullException();
            }

            _database.GetCollection<LongRunningTaskInstanceSnapshot>(CollectionName).InsertMany(snapshots, new InsertManyOptions() { IsOrdered = false });
        }

        /// <summary>
        /// Update task instance snapshot.
        /// </summary>
        /// <param name="snapshot"> Task instance snapshot. </param>
        public void Update(LongRunningTaskInstanceSnapshot snapshot)
        {
            if (snapshot == null)
            {
                throw new LongRunningTaskInstanceSnapshotNullException();
            }

            _database.GetCollection<LongRunningTaskInstanceSnapshot>(CollectionName).FindOneAndReplace(
                Builders<LongRunningTaskInstanceSnapshot>.Filter.Eq(x => x.Id, snapshot.Id),
                snapshot);
        }

        /// <summary>
        /// Remove task instance snapshot.
        /// </summary>
        /// <param name="id"> Task instance ID. </param>
        public void Delete(string id)
        {
            // get collection
            var col = _database.GetCollection<LongRunningTaskInstanceSnapshot>(CollectionName);

            col.DeleteOne(Builders<LongRunningTaskInstanceSnapshot>.Filter.Eq(ent => ent.Id, id));
        }

        /// <summary>
        /// Clear entire collection.
        /// </summary>
        public void Clear()
        {
            _database.GetCollection<LongRunningTaskInstanceSnapshot>(CollectionName).DeleteMany(FilterDefinition<LongRunningTaskInstanceSnapshot>.Empty);
        }
    }
}
